#!/usr/bin/env perl

#
# Copyright (c) 2009 Hatem Nassrat <hnassrat@gmail.com>
# All rights reserved. Modifications under same license as original script.
# ChangeLog:
#
#   Added C-M-Right and C-M-Left for next and prev workspace
#   Added move window to next prev workspace
#   Added dump/restore workspaces
#
# Copyright (c) 2005 Mike O'Connor
# All rights reserved.
# Author Mike O'Connor <stew@vireo.org>
#
# Modified by Shawn Betts.
#
# code was adapeted from rpws that comes from ratpoison containing the follwing copyright:
# Copyright (C) 2003 Shawn Betts
# Author: Shawn Betts
#

use strict;
use warnings;
use Fcntl qw (:flock);
use Getopt::Std;

my $ratpoison = $ENV{ "RATPOISON" } || "ratpoison";
my $tmp=$ENV{ "TMP" } || "/tmp";
my $lockfile = $ENV{ "RPWS_LOCKFILE" } || "$tmp/rpws.$<.lock";

sub help
{
    system("pod2usage", $0);
    print( "for more detailed documentation run \"perldoc $0\"\n" );
}

sub rp_call
{
    my $result = `$ratpoison -c "@_"`;
    chomp( $result );
    chomp( $result );
    return $result;
}

sub ws_init_ws
{

    my $num = shift;

    rp_call( "gnew $num" );
    my $fd = fdump();
    rp_call( "setenv fspl$num $fd" );
}

sub fdump
{
    return rp_call( "fdump" );
}

sub ws_init
{
    my ($num, @names) = @_;

    $num >= 2 || die "You must create at least 2 workspaces.";
    $num == @names || die "You must specify the right number of names.";

    # Backup the frames
    my $fd = fdump();

    rp_call( "gselect 0" );
    rp_call( "grename $names[0]" );
    rp_call( "select -" );
    rp_call( "only" );

    my $i;
    for my $i (2..$num)
    {
        ws_init_ws( $names[$i - 1] );
    }

    # Workspace 1 uses the 'default' group.
    # Start in workspace 1.
    $fd = fdump();
    rp_call( "gselect 0" );
    rp_call( "setenv fspl1 $fd" );
    rp_call( "setenv wspl 1" );

    # Keep track of workspace count
    rp_call( "setenv wspc $num" );

    # restore the frames
    rp_call( "frestore $fd" );

    if( -e "$lockfile" )
    {
        unlink ("$lockfile" );
    }
}

sub ws_save
{
    my $ws = rp_call( "getenv wspl" );
    my $fd = fdump();
    rp_call( "setenv fspl$ws $fd" );
    rp_call( "setenv wspo $ws" );
}

sub ws_restore
{
    my $which = shift;
    my $which_name;
    my $move;

    # Step1: determine which workspace to move to

    if ( $which =~ /^move/ )
    {
        $move = 1;
        $which = substr($which, 4);
    }

    if ( $which eq 'other' )
    {
        my $ws = rp_call( "getenv wspo" );
        $which = $ws || 1;
    }
    elsif ( $which =~ /^(?:next|prev)$/ )
    {
        my $ws = rp_call( "getenv wspl" );
        my $wspc = rp_call( "getenv wspc" );

        if ( $which eq 'next' )
        {
            $ws++;
        }
        else
        {
            $ws--;
        }
        $which = ( ( $ws - 1 ) % $wspc ) + 1;
    }

    $which_name = $which - 1;

    # Step 2a: Moving Window if necessary
    if ( $move )
    {
        rp_call( "gmove $which_name" );
        rp_call( "select -" );
        rp_call( "prev" );
    }

    # Step2: Process the workspace switch
    ws_save();
    rp_call( "gselect $which_name");
    my $last = rp_call( "getenv fspl$which" );
    rp_call( "frestore $last" );
    rp_call( "setenv wspl $which" );

    # Step 2x: Display the moved Window
    if ( $move )
    {
        rp_call( "next" );
    }

    #rp_call( "echo Workspace $which" );

}

sub ws_dump
{
    my $ws_dumpf = shift;
    my $wspc = rp_call( "getenv wspc" );

    open WSDUMP, ">$ws_dumpf" or die $!;

    ws_save();

    for my $i (1..$wspc)
    {
        print WSDUMP rp_call( "getenv fspl$i" )."\n";
    }

    close WSDUMP;

    rp_call( "echo Workspaces dumped to $ws_dumpf" );
}

sub ws_dumprestore
{
    my $ws_dumpf = shift;
    my $ws = rp_call( "getenv wspl" );
    my $wspc = rp_call( "getenv wspc" );

    # Get dumped frame info
    open WSDUMP, "$ws_dumpf" or die $!;
    my @wsdata=<WSDUMP>;
    close WSDUMP;

    my $wspc_dump = scalar @wsdata;

    if ($wspc < $wspc_dump)
    {
        # Number of workspaces to restore more than available
        die "Cannot restore all workspaces (dump_count = " . $wspc_dump . ", rp_count = " . $wspc . ")";
    }

    # Restoring all workspaces
    for my $i (1..$wspc_dump)
    {
        # calculating previous workspace number
        my $j = ( $wspc + $i - 2 ) % $wspc + 1;
        # moving to workspace $i -1
        ws_restore( $j );
        # restoring workspace $i
        rp_call( "setenv fspl$i $wsdata[$i-1]" );
    }

     # goto the originally visible workspace
     ws_restore( $ws );

    rp_call( "echo Workspaces loaded from $ws_dumpf" );
}

sub add_aliases
{
    my $n = shift;
    foreach my $i (1..$n) {
        # Switch Workspace
        rp_call ( "alias rpws$i exec $0 $i" );
        # Move Window to wrokspace
        rp_call ( "alias rpwsm$i exec $0 move$i" );
    }
    rp_call ( "alias rpwso exec $0 other" );
    rp_call ( "alias rpwsn exec $0 next" );
    rp_call ( "alias rpwsp exec $0 prev" );
    rp_call ( "alias rpwsmn exec $0 movenext" );
    rp_call ( "alias rpwsmp exec $0 moveprev" );


}

sub add_keys
{
    my $n = shift;
    foreach my $i (1..$n) {
        # Switch Workspace
        rp_call ( "definekey top M-F$i rpws$i" );
        # Move Window to wrokspace
        rp_call ( "definekey top C-M-S-F$i rpwsm$i" );
    }
    rp_call ( "definekey top C-M-Right rpwsn" );
    rp_call ( "definekey top C-M-Left rpwsp" );
    rp_call ( "definekey top C-M-Up rpwso" );
    rp_call ( "definekey top C-M-greater rpwsmn" );
    rp_call ( "definekey top C-M-less rpwsmp" );
}

my $arg = shift @ARGV || 'help';

if( $arg eq "help" ) {
    help();
} elsif( $arg eq "init" ) {
    my $num = shift @ARGV;
    my %opts;
    getopts('ka', \%opts);
    ws_init( $num, grep {$_ ne '-k' && $_ ne '-a'} @ARGV );
    add_aliases( $num ) if $opts{'a'} || $opts{'k'};
    add_keys ( $num ) if $opts{'k'};
} elsif( $arg eq "dump" ) {
    my $filename = shift @ARGV;
    ws_dump($filename);
} elsif( $arg eq "restore" ) {
    my $filename = shift @ARGV;
    ws_dumprestore($filename);
} else {
   open LOCK, ">>$lockfile" or die "Cannot open lockfile: $lockfile";
   flock(LOCK, LOCK_EX);
   ws_restore( $arg );
}

__END__

=head1 NAME

rpws - Implements multiple workspaces in ratpoison

=head1 SYNOPSIS

 rpws init n [-k] [-a]  - setup rpws with n workspaces.
                            -a sets up command aliases;
                            -k sets up key bindings and aliases.
 rpws dump <fname>      - dumps the current layout to <fname>
 rpws restore <fname>   - restores rpws workspaces from <fname>
 rpws help              - this documentation
 rpws n                 - switch to this workspace


=head1 DESCRIPTION

 B<rpws> implements multiple workspaces in ratpoison by making calls
 to fdump, freestore.  It was adapted from rpws which comes with
 ratpoison in the contrib directory.

=head1 USAGE

Add the following line in ~/.ratpoisonrc

     exec /path/to/rpws init 6 -k

This creates 6 aliases rpws1, rpws2, etc. It also binds the keys M-F1,
M-F2, etc to each rpwsN alias. Moreover, rpwsn (Next) and rpwsp (Prev) are
created, and C-M-{Right,Left} are bound to rpws{n,p}. Full list of keybindings
created are:

    M-F$i           Goto workspace $i
    C-M-Right       Goto Next workspace
    C-M-Left        Goto Prev workspace
    C-M-S-F$i       Move window to workspace $i
    C-M-greater     Move current window to next workspace
    C-M-less        Move current window to prev workspace

=head1 FILES

 rpws requires use of a lockfile.  It defaults to using
/tmp/rpws.<UID>.lock but this can be changed by setting the
environment variable RPWS_LOCKFILE to your desired lockfile.

=head1 AUTHOR

 Mike O'Connor <stew@vireo.org>

=head1 COPYRIGHT

 Copyright (c) 2005 Mike O'Connor
 All rights reserved.

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
